#coding=utf-8

from __future__ import print_function

import datetime
from sys import getsizeof, stderr
from itertools import chain
from collections import deque
from reprlib import repr
import winsound
import numpy as np
import pytz
import calendar as calendar
from Core.Instrument import *
import json
import os
import pandas as pd
import random
import decimal
from datetime import *
from xlrd import xldate_as_tuple


def DeleteTimeZone(datetime1):
    return datetime1.replace(tzinfo = None)


def FilterWindDateTimeDirty(dt):
    return datetime(dt.year, dt.month, dt.day, dt.hour,dt.minute, dt.second)


def FutureSymbolToProduct(symbol):
    out = ""
    for s in symbol:
        if s.isdigit():
            return out
        else:
            out = out + s


def FindElement(list, target):
    for element in list:
        found = True
        for k,v in target.items():
            if element[k] != v:
                found = False
                break
        if found == True:
            return element
    return None


def FindInList(list, target):
    for i in range(len(list)):
        element = list[i]
        if element == target:
            return i
    return -1


def FindIndex(dataSeries, datetime, option="Previous", datetimeField="StdDateTime"):
    if len(dataSeries) == 0:
        return None
    index = GetIndex(dataSeries, datetime, 0, len(dataSeries) - 1, 0, option, datetimeField)
    if index == None:
        return None
    if index == -1:
        return None
    if index >= len(dataSeries):
        return None
    return index


def Find(dataSeries, datetime, option="Previous", datetimeField="StdDateTime"):
    index = FindIndex(dataSeries, datetime, option=option, datetimeField=datetimeField)
    #
    if index == None:
        return None
    #
    return dataSeries[index]


# --- Listed before datetime1 ----
# 不再使用区间计算法 20200220
def FindListedInstrument(database,
                         datetime2,
                         datetime1=None,
                         instrumentType="Stock",
                         noNewStock=False,
                         filter={}):

    df = FindListedInstrument_DataFrame(database,
                                        datetime2,
                                        datetime1=datetime1,
                                        instrument_type=instrumentType,
                                        no_new_stock=noNewStock,
                                        extra_filter=filter)
    documents = df.to_dict(orient='records')
    return documents


# 若指定DateTime2， 在DateTime2 没退市的股票
# 若指定DateTime1， 在DateTime1 已经上市交易的
def FindListedInstrument_DataFrame(database,
                                   datetime2,
                                   datetime1=None,
                                   instrument_type="Stock",
                                   no_new_stock=False, extra_filter={}):
    #
    filtered_instruments = database.Find("Instruments", instrument_type)
    if len(filtered_instruments) == 0:
        return pd.DataFrame()

    df = DocumentsToDataFrame(filtered_instruments)
    #
    convertFields = {}
    convertFields["datetime1"] = "DateTime1"
    convertFields["datetime2"] = "DateTime2"
    convertFields["symbol"] = "Symbol"
    convertFields["description"] = "Description"
    #
    for field, convertTo in convertFields.items():
        if field in df.columns:
            df.rename(columns={field: convertTo}, inplace=True)

    # 没退市， 这个时间仍然在市，正常交易
    df = df[df["DateTime2"] > datetime2]

    # 已上市，同时还必须满足 DateTime2 已经上市
    if no_new_stock:
        early_datetime = datetime2 - timedelta(days=365)
    else:
        early_datetime = datetime2

    # 一般不指定开始时间，如若指定，确保此时股票已经上市
    if datetime1:
        if no_new_stock:
            early_datetime = datetime1 - timedelta(days=365)
        else:
            early_datetime = datetime1

    df = df[df["DateTime1"] <= early_datetime]

    # 用外部filter进行过滤
    if len(extra_filter) > 0:
        for field, value in extra_filter.items():
            df = df[df[field] == value]

    #
    return df


# ---Fix bug, no "Excat" situation 2018-4-10 ---
def GetIndex(dataSeries, datetime, beginIndex, endIndex, recursive, option, datetimeField):
    length = endIndex - beginIndex + 1
    beginDatetime = dataSeries[beginIndex][datetimeField]
    endDatetime = dataSeries[endIndex][datetimeField]

    #-------------------------------
    #---Special Case---
    if datetime < beginDatetime:
        if option == "Previous":
            return beginIndex - 1
        if option == "Next":
            return beginIndex
        if option == "Exact":
            return -1
    if datetime > endDatetime:
        if option == "Previous":
            return endIndex
        if option == "Next":
            return endIndex + 1
        if option == "Exact":
            return -1

    if datetime == beginDatetime:
        return beginIndex

    if datetime == endDatetime:
        return endIndex

    #-------------------------------
    #if length == 1:
    #    if option == "Previous":
    #        return beginIndex - 1
    #    if option == "Next":
    #        return beginIndex
    #    #other "Exect"
    #    return None

    if length == 2:
        #---只剩下中间状态了---
        if option == "Previous":
            return beginIndex
        if option == "Next":
            return endIndex
        return -1

    else:
        midIndex = beginIndex + int((length * 0.5))
        tmpDateTime = dataSeries[midIndex][datetimeField]
        # tmpDateTime = tmpDateTime.replace(tzinfo=None)
        if datetime <= tmpDateTime:
            return GetIndex(dataSeries, datetime, beginIndex, midIndex, recursive+1, option, datetimeField)
        else:
            return GetIndex(dataSeries, datetime,midIndex, endIndex, recursive+1, option, datetimeField)

    return -1


# 输入年和数字报告期，返回 report date
def GenerateReportDate(year, period, isUTC = True):
    #
    if period == 1:
        reportDate = datetime(year, 3, 31)
    elif period == 2:
        reportDate = datetime(year, 6, 30)
    elif period == 3:
        reportDate = datetime(year, 9, 30)
    else:
        reportDate = datetime(year, 12, 31)
    #
    if isUTC:
        return ToUTCDateTime(reportDate)
    else:
        return reportDate


#
def MonthToPeriod(month):
    if month in [1, 2, 3]:
        return 1
    elif month in [4, 5, 6]:
        return 2
    elif month in [7, 8, 9]:
        return 3
    elif month in [10, 11, 12]:
        return 4
    else:
        print("Unknown Month", month)
        return np.nan

#
def GenerateNextReportDate(reportDate, offset=1):
    return GeneratePreviousReportDate(reportDate, offset=offset)


#
# offset -1 : 上一期
# offset +1 : 下一期
def GeneratePreviousReportDate(reportDate, offset=-1):
    year = reportDate.year
    month = reportDate.month
    period = MonthToPeriod(month)

    offsetPeriod = period + offset
    #
    quotient = offsetPeriod // 4
    remainder = offsetPeriod % 4

    targetYear = year + quotient
    if offsetPeriod >= 0:# 后移
        targetPeriod = remainder
        if remainder == 0:
            targetYear -= 1
            targetPeriod = 4
    else: # 前移
        targetPeriod = remainder
        if remainder == 0:
            targetYear -= 1
            targetPeriod = 4
    #
    return GenerateReportDate(targetYear, targetPeriod, isUTC=False)


# ---给定起始日期，获取中间包括的报告日日期，report date ---
def generate_report_date_list(datetime1, datetime2, asDate=False):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 3 and reportDate.day == 31)
            or
            (reportDate.month == 6 and reportDate.day == 30)
            or
            (reportDate.month == 9 and reportDate.day == 30)
            or
            (reportDate.month == 12 and reportDate.day == 31)):
            newReportDate = datetime(reportDate.year, reportDate.month, reportDate.day)
            if asDate:
                newReportDate = newReportDate.date()
            reportDateTimes.append(newReportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


def generate_h1h2_date_list(datetime1, datetime2, as_date=False):
    report_date_times = []
    report_date = datetime1
    while report_date <= datetime2:
        if ((report_date.month == 6 and report_date.day == 30)
            or
            (report_date.month == 12 and report_date.day == 31)):
            new_report_date = datetime(report_date.year, report_date.month, report_date.day)
            if as_date:
                new_report_date = new_report_date.date()
            report_date_times.append(new_report_date)
        report_date = report_date + timedelta(days=1)
    return report_date_times


# ---获取每月第一天---
def generate_begin_date_of_month_list(datetime1, datetime2, as_date=False, is_utc=False):
    #
    if datetime1.tzinfo != None:
        is_utc = True
    else:
        is_utc = False
    #
    if is_utc:
        localDateTime1 = ToLocalDateTime(datetime1)
        localDateTime2 = ToLocalDateTime(datetime2)
    else:
        localDateTime1 = datetime1
        localDateTime2 = datetime2
    #
    datetimes = []
    for year in range(localDateTime1.year, localDateTime2.year + 1):
        for month in range(1, 13):
            beginDayofMonth = datetime(year, month, 1)
            #
            if is_utc:
                utcBeginDayofMonth = ToUTCDateTime(beginDayofMonth)
            else:
                utcBeginDayofMonth = beginDayofMonth
            #
            tmpDatetime = utcBeginDayofMonth
            if as_date:
                tmpDatetime = utcBeginDayofMonth.date()
            #
            if datetime1 > utcBeginDayofMonth:
                continue
            if datetime2 < utcBeginDayofMonth:
                continue
            datetimes.append(tmpDatetime)
    #
    return datetimes


def generate_end_date_of_month_list(datetime1, datetime2, as_date=False):
    # localDateTime1 = ToLocalDateTime(datetime1)
    # localDateTime2 = ToLocalDateTime(datetime2)
    localDateTime1 = datetime1
    localDateTime2 = datetime2
    # if isinstance(datetime1, datetime):
    #     localDateTime1 = datetime1.date()
    #     localDateTime2 = datetime2.date()
    #
    datetimes = []
    for year in range(localDateTime1.year, localDateTime2.year + 1):
        for month in range(1, 13):
            days = calendar.monthrange(year, month)
            if as_date:
                endDayofMonth = date(year, month, days[1])
            else:
                endDayofMonth = datetime(year, month, days[1])
            # endDayofMonth = endDayofMonth + timedelta(days=-1)
            if localDateTime1 > endDayofMonth:
                continue
            if localDateTime2 < endDayofMonth:
                continue
            datetimes.append(endDayofMonth)
    return datetimes


def generate_begin_trading_date_of_month_list(database, datetime1, datetime2, as_date=False):
    res_date_list = []
    date_list = generate_begin_date_of_month_list(datetime1, datetime2, as_date=as_date)
    for update_datetime in date_list:
        recent_trading_date = find_recent_trading_day(database, update_datetime, prev_next="next")
        # print(recent_trading_date)
        res_date_list.append(recent_trading_date)
    #
    return res_date_list


def generate_end_trading_date_of_month_list(database, datetime1, datetime2, as_date=False, as_df=False):
    res_date_list = []
    df_bm_bars = database.GetDataFrame("financial_data", "index_daily_bar", filter=[("symbol", "000001.SH")], sort=[("date", 1)])
    df_bm_bars = df_bm_bars[(df_bm_bars["date"]>=datetime1)&(df_bm_bars["date"]<=datetime2)]
    df_bm_bars['date_t'] = pd.to_datetime(df_bm_bars['date'])
    df_bm_bars.set_index("date_t", inplace=True)
    df_date_monthly = df_bm_bars.resample("ME").last()
    #
    if as_df:
        return df_date_monthly["date"]
    else:
        return list(df_date_monthly["date"])


# ---5.1, 9.1 和 11.1(12.1)?
def generate_release_date_list(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 5 and reportDate.day == 1)
            or
            (reportDate.month == 9 and reportDate.day == 1)
            or
            (reportDate.month == 11 and reportDate.day == 1)):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


# ---430, 0831  和 1031
def generate_release_date_list_meb(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 4 and reportDate.day == 30)
            or
            (reportDate.month == 8 and reportDate.day == 31)
            or
            (reportDate.month == 10 and reportDate.day == 31)):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


# 只年报日期
def generate_annual_report_release_date_list(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if (reportDate.month == 5 and reportDate.day == 1):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes

# 基金的报告发布日
def generate_fund_release_date_list(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 2 and reportDate.day == 1)
            or
            (reportDate.month == 5 and reportDate.day == 1)
            or
            (reportDate.month == 8 and reportDate.day == 1)
            or
            (reportDate.month == 11 and reportDate.day == 1)):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


def generate_fund_release_date_list_meb(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 1 and reportDate.day == 31)
            or
            (reportDate.month == 4 and reportDate.day == 30)
            or
            (reportDate.month == 7 and reportDate.day == 31)
            or
            (reportDate.month == 10 and reportDate.day == 31)):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


def find_fund_release_date(any_datetime, offset=0, use_month_end=False):
    #
    days = np.abs(offset * 91) + 91
    days = int(days)
    #
    if offset <= 0: # 向前找最近的一个
        datetime1 = any_datetime - timedelta(days=days)
        datetime2 = any_datetime
        if use_month_end:
            month_dates = generate_fund_release_date_list_meb(datetime1, datetime2)
        else:
            month_dates = generate_fund_release_date_list(datetime1, datetime2)
        return month_dates[offset - 1]
    #
    else: # "Next"
        datetime1 = any_datetime + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = any_datetime + timedelta(days=days)
        if use_month_end:
            month_dates = generate_fund_release_date_list_meb(datetime1, datetime2)
        else:
            month_dates = generate_fund_release_date_list(datetime1, datetime2)
        return month_dates[offset - 1]


def GenerateCloseTime(year, month, day):
    datetime1 = datetime(year, month, day)
    datetime1 = datetime1 + timedelta(hours=15)
    return ToUTCDateTime(datetime1)


def GenerateDateRange(datetime1, datetime2):
    datetimes = []
    dt = datetime1
    while dt <= datetime2:
        datetimes.append(dt)
        dt += timedelta(days=1)

    return datetimes


def LoadConstitutes(database, indexSymbol, datetime2=None):

    instruments = []
    instrumentList_TimeSeries = database.getDataSeries(indexSymbol + "_InstrumentList")

    # --- Old Fashion---
    # instrumentsList = instrumentSeries[instrumentList_TimeSeries.count()-1]

    if datetime2 == None:
        datetime2 = datetime.now()
    datetime2 = ToUTCDateTime(datetime2)

    instrumentsList = instrumentList_TimeSeries.Get(datetime2)

    for content in instrumentsList["Values"]:
        symbol = content["Symbol"]
        weight = content["Weight"]
        instruments.append({"Symbol": symbol, "Weight": weight})

    return instruments


#---读取证券列表---
def LoadInstruments(database, instrumentsByName, maxNum):
    instruments = []
    instrumentSeries = database.findAll("Instruments","Stock")
    i = -1
    for obj in instrumentSeries:
        i = i+1
        if maxNum != 0:
            if(i >= maxNum):
                break
        instrument = Instrument(obj["Symbol"], obj["Description"], obj["Type"]) #建立一个Instrument对象
        if "Industry" in obj["Properties"]:
            instrument.industry = obj["Properties"]["Industry"] #补充一个行业信息进去
        else:
            print("Symbol " + obj["Symbol"] + " Not Include Industry Info!!!")
        instrumentsByName[instrument.symbol] = instrument #把instrument们放进一个大容器（字典）
        instruments.append(instrument)
    return instruments


def LoadInstruments2(database, maxNum):
    instruments = []
    instrumentSeries = database.findAll("Instruments","Stock")
    i = -1
    for obj in instrumentSeries:
        i = i+1
        if maxNum != 0:
            if(i >= maxNum):
                break
        instruments.append(obj["Symbol"])
    return instruments


def MinDateTime():
    minDateTime = datetime(1900, 1, 1)
    # minDateTime = ToUTCDateTime(minDateTime)
    return minDateTime


def MaxDateTime():
    maxDateTime = datetime(2100, 1, 1)
    maxDateTime = ToUTCDateTime(maxDateTime)
    return maxDateTime


def MidNight(datetime1):
    #
    return datetime(datetime1.year, datetime1.month, datetime1.day) + timedelta(days=1)


# --- 2010-05-04 14:20:01.500 ---
# --- "%Y/%m/%d %H:%M:%S.%f" ---
# --- "%Y-%m-%d-%H" ---
def ParseDateTime(strDateTime, format = "%Y-%m-%d %H:%M:%S.%f"):
     return datetime.strptime(strDateTime, format)


def PlaySound():
    soundFile = 'd:\Sound\BeepLoop.wav'

    while(1):
        winsound.PlaySound(soundFile,winsound.SND_FILENAME)
        #winsound.PlaySound("SystemExit", winsound.SND_ALIAS)


def PageNavigation(page,button,maxLength):

    #FrontEnd = parseInt(FrontEnd)
    pageSize = 20
    maxPage = int(maxLength / pageSize) + 1

    if(button == "nextPage"):
        page += 1
    elif(button == "prePage"):
        page-=1
    elif(button == "lastPage"):
        page = maxPage
    elif(button == "firstPage"):
        page = 1

    if(page<1):
        page = 1
    if(page > maxPage):
        page = maxPage

    begin = (page - 1) * pageSize
    end = page * pageSize
    if (end > maxLength):
        end = maxLength - 1

    pageNavigation = {
        "Page":page,
        "Begin":begin,
        "End": end,
        "Size": pageSize,
        "MaxPage":maxPage}

    return pageNavigation


def StdDateTimeToTradeDate(stdDateTime):
    localDateTime = ToLocalDateTime(stdDateTime)
    return ToDate(localDateTime)


# ---DateTime convert to String of "2016-01-01"---
def ToDateString(date):
        #dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
        return date.strftime('%Y-%m-%d')


# ---DateTime convert to String of "20160101"---
def ToDateString2(date):
        #dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
        return date.strftime('%Y%m%d')

def ToDateString3(date):
 	#dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
    return str(date.year)+ "/" + str(date.month) + "/" + str(date.day)


def ToDateTimeString(date):
    s = date.strftime('%Y-%m-%d %H:%M:%S.%f')
    return s[:-3]


def ToDateTimeString2(date):
    return date.strftime('%Y-%m-%d %H:%M:%S.%f')


#---过滤Wind中毫秒脏数据现象---
def ToDateTimeString3(date):
    return date.strftime('%Y-%m-%d %H:%M:%S' + ".000")


def ToUTCDateTime(datetime1):
    #datetime2 = datetime1 + timedelta(hours=-8)
    #datetime2 = datetime2.replace(tzinfo=pytz.utc)

    # min = datetime(1950, 1, 1)
    # if datetime1 < min:
    #    datetime1 = min

    utc = pytz.utc
    datetime2 = datetime1.astimezone(utc)
    return datetime2


def ToLocalDateTime(datetime1):
    #datetime2 = datetime1 + timedelta(hours=+8)
    #datetime2 = datetime2.replace(tzinfo = pytz.timezone("Asia/Shanghai"))

    #min = datetime(1970, 1, 3)
    #if datetime1 < min:
    #    datetime1 = min

    local_Timezone = pytz.timezone("Asia/Shanghai")
    datetime2 = datetime1.astimezone(local_Timezone)
    return datetime2


def ToDate(datetime1):
    return datetime(datetime1.year, datetime1.month, datetime1.day)


def ToClosingDateTime(datetime1):
    datetime1 = ToLocalDateTime(datetime1)
    datetime2 = datetime(datetime1.year, datetime1.month, datetime1.day, 15, 0, 0)
    return ToUTCDateTime(datetime2)


def DateToDateTime(date1):
    return datetime.combine(date1, datetime.min.time())


# ---Return 报告期 1,2,3,4---
def report_date_to_period(report_date):
    if (report_date.month == 3 and report_date.day == 31):
        return 1;
        #modifiedDateTime = releaseDateTime + timedelta(seconds = 1)#防止第一季度报告和年报时间重合
    elif (report_date.month == 6 and report_date.day == 30):
        return 2;
    elif (report_date.month == 9 and report_date.day == 30):
        return 3;
    elif (report_date.month == 12 and report_date.day == 31):
        return 4;
    else:
        print("Unresolve Period");
        #print("Unresolve Period : " + symbol);
        return None


def ResampledDataSeries(dataSeries, datetimeSeries):
    resampledSeries = []
    i = 0
    for datetime0 in datetimeSeries:
        element = Find(dataSeries, datetime0)
        i = i + 1
        if element == None:
            continue
        resampledSeries.append(element)
    return resampledSeries


def ReadFolder(filepath):
    filenames = []
    pathDir =  os.listdir(filepath)
    for allDir in pathDir:
        child = os.path.join('%s%s' % (filepath, allDir))
        #print child.decode('gbk')
        filenames.append(allDir)

    return filenames


def TotalSize(o, handlers={}, verbose=False):
    """ Returns the approximate memory footprint an object and all of its contents.

    Automatically finds the contents of the following builtin containers and
    their subclasses:  tuple, list, deque, dict, set and frozenset.
    To search other containers, add handlers to iterate over their contents:

        handlers = {SomeContainerClass: iter,
                    OtherContainerClass: OtherContainerClass.get_elements}

    """
    dict_handler = lambda d: chain.from_iterable(d.items())
    all_handlers = {tuple: iter,
                    list: iter,
                    deque: iter,
                    dict: dict_handler,
                    set: iter,
                    frozenset: iter,
                   }
    all_handlers.update(handlers)     # user handlers take precedence
    seen = set()                      # track which object id's have already been seen
    default_size = getsizeof(0)       # estimate sizeof object without __sizeof__

    def sizeof(o):
        if id(o) in seen:       # do not double count the same object
            return 0
        seen.add(id(o))
        s = getsizeof(o, default_size)

        if verbose:
            print(s, type(o), repr(o), file=stderr)

        for typ, handler in all_handlers.items():
            if isinstance(o, typ):
                s += sum(map(sizeof, handler(o)))
                break
        return s

    return sizeof(o)


def SortDict(dict, reverse=False):
    sorted(dict.items(), key=lambda x: x[1], reverse=reverse)


def CreateFolder(fullPathname):
    existed = os.path.exists(fullPathname)
    if not existed:
        os.makedirs(fullPathname)
        print("Create Folder: " + fullPathname)
    else:
        #print("Folder Existed: " + fullPathname)
        pass


def GenerateTimeRange_Yearly(datetime1, datetime2, baseDate=datetime(2000, 5, 1)):
    datatimeRanges = []
    for year in range(datetime1.year, datetime2.year + 1):
        begin = datetime(year, baseDate.month, baseDate.day)
        end = datetime(year + 1, baseDate.month, baseDate.day)
        #
        if begin < datetime1 or end > datetime2:
            continue
        #
        datatimeRanges.append([begin, end])
    #
    return datatimeRanges


def GenerateTimeRange_HalfYear(datetime1, datetime2, baseDate=datetime(2000, 5, 1)):
    #
    curDateTime = datetime(datetime1.year, baseDate.month, baseDate.day)
    day = baseDate.day
    #
    datatimeRanges = []
    lastDateTime = curDateTime
    while curDateTime <= datetime2:
        month = curDateTime.month + 6
        if month > 12:
            curDateTime = datetime(curDateTime.year+1, month%12, day)
        else:
            curDateTime = datetime(curDateTime.year, month, day)

        if curDateTime > datetime2:
            break
        #
        datatimeRanges.append([lastDateTime, curDateTime])
        lastDateTime = curDateTime
    #
    return datatimeRanges


def generate_time_range_quarterly(datetime1, datetime2):
    datetime_ranges = []
    for year in range(datetime1.year, datetime2.year+1):
        for q in range(4):
            if q == 0:
                begin_date = datetime(year, 1, 1)
                end_date = datetime(year, 3, 31)
            elif q == 1:
                begin_date = datetime(year, 4, 1)
                end_date = datetime(year, 6, 30)
            elif q == 2:
                begin_date = datetime(year, 7, 1)
                end_date = datetime(year, 9, 30)
            elif q == 3:
                begin_date = datetime(year, 10, 1)
                end_date = datetime(year, 12, 31)
            #
            if (begin_date >= datetime1) and (end_date <= datetime2):
                datetime_ranges.append([begin_date, end_date])

    #
    return datetime_ranges


def GenerateTimeRange_Monthly(datetime1, datetime2):
    #
    ret = []
    for year in range(datetime1.year, datetime2.year + 1):
        for month in range(1, 13):
            res = calendar.monthrange(year, month)
            num_day_of_month = res[1]
            begin_day_of_month = datetime(year, month, 1)
            end_day_of_month = datetime(year, month, num_day_of_month)

            if begin_day_of_month < datetime1:
                continue
            if end_day_of_month > datetime2:
                continue
            ret.append([begin_day_of_month, end_day_of_month])
    #
    return ret


def InstrumentsToSymbols(instruments):
    #
    symbols = []
    for instrument in instruments:
        symbols.append(instrument["Symbol"])
    #
    return symbols


def InstrumentToDateFrame(instruments):
    #
    symbols = []
    for instrument in instruments:
        symbols.append(instrument["Symbol"])
    #
    return pd.DataFrame(symbols, columns=["Symbol"])


def SymbolsToDateFrame(symbols):
    #
    return pd.DataFrame(symbols, columns=["Symbol"])


def DateTimeRange(datetime1, datetime2, datetimeField="StdDateTime"):
    #
    filter = {}
    filter[datetimeField] = {}
    if datetime1 != None:
        filter[datetimeField]["$gte"] = datetime1
    if datetime2 != None:
        filter[datetimeField]["$lte"] = datetime2
    if not filter[datetimeField]:
        del filter[datetimeField]
    #
    return filter


# {"Date": "Date", "Value": "Return"}
def DocumentsToDataFrame(documents, rename={}, drop=[], keep=[], index=None):
    data = []
    if len(documents) == 0:
        return pd.DataFrame()

    # ---确定存在哪些Field---
    # 抽样第一个和最后一个文档，用他们字段的并集
    possibleFields = list(documents[0].keys())
    for key, value in documents[-1].items():
        if key not in possibleFields:
            possibleFields.append(key)

    fields = []
    for field in possibleFields:
        # ---如果要索引，索引字段一定要保留---
        if field == index:
            fields.append(field)
            continue
        #
        if len(drop) > 0 and field in drop:
            continue
        if len(keep) > 0 and field not in keep:
            continue
        #
        fields.append(field)

    # ---Content---
    for d in documents:
        row = []
        for f in fields:
            value = d.get(f)
            if isinstance(value, decimal.Decimal):
                value = float(value)
            row.append(value)
        data.append(row)

    # ---Build DataFrame---
    dfPf = pd.DataFrame(data, columns=fields)
    # print(dfPf.head())
    #
    if index !=None:
        dfPf.set_index(index, inplace=True)

    # ---有些需要转换格式---
    # 强制将Date变为Datetime会带来很多问题，比如Date之间不能比较和加减运算
    # convertToDateTime = ["date", "Date"]
    # for field in convertToDateTime:
    #     if field in dfPf.columns:
    #         dfPf[field] = pd.to_datetime(dfPf[field])

    # ---Rename---
    dfPf.rename(columns=rename, inplace=True)

    #
    return dfPf


# 前后寻找月份日
# 可以指定月初日还是月末日
def find_month_date(any_datetime, offset=0, use_month_end=True):
    #
    days = np.abs(offset * 31) + 31
    days = int(days)
    #
    if offset <= 0: # 向前找最近的一个
        datetime1 = any_datetime - timedelta(days=days)
        datetime2 = any_datetime
        if use_month_end:
            month_dates = generate_end_date_of_month_list(datetime1, datetime2)
        else:
            month_dates = generate_begin_date_of_month_list(datetime1, datetime2)
        return month_dates[offset - 1]
    #
    else: # "Next"
        datetime1 = any_datetime + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = any_datetime + timedelta(days=days)
        if use_month_end:
            month_dates = generate_end_date_of_month_list(datetime1, datetime2)
        else:
            month_dates = generate_begin_date_of_month_list(datetime1, datetime2)
        return month_dates[offset - 1]


# 最近的月末日期
def find_recent_end_date_of_month(any_datetime):
    return find_month_date(any_datetime, offset=0, use_month_end=True)


# 普通寻找月末日
def find_end_date_of_month(any_datetime):
    this_month_end = datetime(any_datetime.year, any_datetime.month, calendar.monthrange(any_datetime.year, any_datetime.month)[1])
    return this_month_end


# 找到上个月的最后一天，不在意当前是否是月末
def find_last_end_date_of_month(update_datetime):
    datetime_tmp = datetime(update_datetime.year, update_datetime.month, 1)
    return datetime_tmp - timedelta(days=1)


#
def find_recent_report_date(current_datetime):

    reportDate = None
    if current_datetime < datetime(current_datetime.year, 5, 1):
        #
        reportDate = datetime(current_datetime.year - 1, 9, 30) # last year Q3
    elif current_datetime < datetime(current_datetime.year, 9, 1):
        #
        reportDate = datetime(current_datetime.year, 3, 31)
    elif current_datetime < datetime(current_datetime.year, 11, 1):
        #
        reportDate = datetime(current_datetime.year, 6, 30)
    else:
        reportDate = datetime(current_datetime.year, 9, 30)
    #
    return reportDate


# 不是最近的报告期都已经被发布
# 430,831 和 1031 无法取到因子
def find_recent_released_report_date(datetime2):
    year = datetime2.year
    #
    if datetime2.month >= 11:
        report_date = datetime(year, 9, 30)
    elif datetime2.month >= 9:
        report_date = datetime(year, 6, 30)
    elif datetime2.month >= 5:
        report_date = datetime(year, 3, 31)
    else:
        report_date = datetime(year-1, 9, 30)
    #
    return report_date


# 430, 831 和 1031 可以取到因子
# meb, month end based
def find_recent_released_report_date_meb(datetime2):
    year = datetime2.year
    #
    if datetime2 >= datetime(year, 10, 31):
        report_date = datetime(year, 9, 30)
    elif datetime2 >= datetime(year, 8, 31):
        report_date = datetime(year, 6, 30)
    elif datetime2 >= datetime(year, 4, 30):
        report_date = datetime(year, 3, 31)
    else:
        report_date = datetime(year-1, 9, 30)
    #
    return report_date


def find_recent_released_annual_report_date(datetime2):
    year = datetime2.year
    month = datetime2.month
    #
    if month >= 5: # 5 - 12
        report_date = datetime(year-1, 12, 31)
    else: # 1 - 4
        report_date = datetime(year-2, 12, 31)
    #
    return report_date


def get_month_diff_v2(datetime1, datetime2):
    year_diff = datetime2.year - datetime1.year
    month_diff = datetime2.month - datetime1.month
    return 12 * year_diff + month_diff





# 找到最近年报日期
def find_recent_annual_report_date(datetime2):
    year = datetime2.year
    #
    if datetime2.month >= 5:
        report_date = datetime(year-1, 12, 31)
    else:
        report_date = datetime(year-2, 12, 31)
    #
    return report_date


# 找到最近的年报半年报日期
def find_recent_h1h2_report_date(datetime2):
    year = datetime2.year
    #
    if datetime2.month < 5:
        report_date = datetime(year-1, 6, 30)
    elif datetime2.month < 7:
        report_date = datetime(year-1, 12, 31)
    else:
        report_date = datetime(year, 6, 30)
    #
    return report_date


#
def report_date_to_h1h2_report_date(report_date):
    year = report_date.year
    #
    if report_date.month == 3:
        report_date = datetime(year-1, 12, 31)
    elif report_date.month == 9:
        report_date = datetime(year, 6, 30)
    #
    return report_date


#
def find_report_date(current_datetime, offset=0):
    days = np.abs(offset * 95) + 95
    days = int(days)
    if days < 365:
        days = 365
    #
    if offset <= 0: # 向前找最近的一个
        datetime1 = current_datetime - timedelta(days=days)
        datetime2 = current_datetime
        report_dates = generate_report_date_list(datetime1, datetime2)
        return report_dates[offset-1]

    # elif offset < 0: # previous 前序寻找
    #     datetime1 = target_datetime - timedelta(days=days)
    #     datetime2 = target_datetime - timedelta(days=1) # 避免当天是Report Date的情况
    #     report_dates = GenerateReportDates(datetime1, datetime2)
    #     return report_dates[offset-1]
    #
    else: # "Next"
        datetime1 = current_datetime + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = current_datetime + timedelta(days=days)
        report_dates = generate_report_date_list(datetime1, datetime2)
        return report_dates[offset-1]


def find_release_date(datetime2, offset=0, use_month_end=False):
    year = np.abs(offset // 4) + 1
    days = (year + 1) * 365
    if offset <= 0:
        datetime1 = datetime2 + timedelta(days = -1.0 * days)
        #
        if use_month_end:
            release_dates = generate_release_date_list_meb(datetime1, datetime2)
        else:
            release_dates = generate_release_date_list(datetime1, datetime2)
        #
        return release_dates[offset - 1]
    else:
        datetime1 = datetime2 + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = datetime1 + timedelta(days = 1.0 * days)
        #
        if use_month_end:
            release_dates = generate_release_date_list_meb(datetime1, datetime2)
        else:
            release_dates = generate_release_date_list(datetime1, datetime2)
        #
        return release_dates[offset - 1]


def report_date_to_release_date(report_date, use_month_end=False):
    year = report_date.year
    if report_date.month == 3: ##
        return datetime(year, 5, 1)
    elif report_date.month == 6:
        return datetime(year, 9, 1)
    elif report_date.month == 9:
        return datetime(year, 11, 1)
    elif report_date.month == 12:
        return datetime(year+1, 5, 1)
    else:
        return None


# 公募基金季报披露日期，假设为季度月的下个月
# 季报：每季结束之日起15个工作日内
# 半年报：在上半年结束之日起60日内 0830
# 年报：每年结束之日起90日内 0331
def report_date_to_mutualfund_quarterly_report_release_date(report_date):
    year = report_date.year
    if report_date.month == 3:  ##
        return datetime(year, 4, 30)
    elif report_date.month == 6:
        return datetime(year, 7, 31)
    elif report_date.month == 9:
        return datetime(year, 10, 31)
    elif report_date.month == 12:
        return datetime(year + 1, 1, 31)
    else:
        return None


def find_mutualfund_recent_report_date(current_datetime):
    if current_datetime < datetime(current_datetime.year, 1, 31):
        report_date = datetime(current_datetime.year - 1, 9, 30)  # last year Q3

    elif current_datetime < datetime(current_datetime.year, 4, 30):
        report_date = datetime(current_datetime.year - 1, 12, 31)

    elif current_datetime < datetime(current_datetime.year, 7, 31):
        report_date = datetime(current_datetime.year, 3, 31)

    elif current_datetime < datetime(current_datetime.year, 10, 31):
        report_date = datetime(current_datetime.year, 6, 30)

    else:
        report_date = datetime(current_datetime.year, 9, 30)
    #
    return report_date


def Generate_Calender_Days_DataFrame(datetime1, datetime2, date_field_name="Date", date_as_index=False):
    dt = datetime1
    data = []
    while dt <= datetime2:
        data.append(dt)
        dt += timedelta(days=1)
    df = pd.DataFrame(data, columns=[date_field_name])
    #
    df[date_field_name] = pd.to_datetime(df[date_field_name]) # 不转换本身就是DateTime
    #
    if date_as_index:
        df.set_index(date_field_name, inplace=True)
    #
    return df


def generate_calender_day(datetime1, datetime2):
    dt = datetime1
    data = []
    while dt <= datetime2:
        data.append(dt)
        dt += timedelta(days=1)
    #
    return pd.DataFrame(data, columns=['date'])


def generate_calender_day_list(datetime1, datetime2):
    dt = datetime1
    data = []
    while dt <= datetime2:
        data.append(dt)
        dt += timedelta(days=1)
    return data


# backforword_1_day 是否需要再向前回溯一天
def generate_trading_days(database, datetime1, datetime2,
                          date_field="date", date_as_type=False, date_as_index=False, as_df=True,
                          backforword_1_day=False):

    # datetime2_mid_night = MidNight(datetime2)
    df = database.GetDataFrame("financial_data", "index_dailybar", filter=[("symbol", "000001.SH")], projection=["date"])

    #
    if backforword_1_day:
        pass
    else:
        df = df[(df["date"]>=datetime1) & (df["date"]<=datetime2)].copy()
        df.reset_index(inplace=True)

    # date 和 datetime互换
    if not date_as_type:
        df["date"] = pd.to_datetime(df["date"])

    # 是否需要重命名字段
    if date_field != "date":
        df.rename(columns={"date":date_field}, inplace=True)

    if not as_df:
        return list(df[date_field])

    if date_as_index:
        df.set_index(date_field, inplace=True)

    return df


def Generate_Trading_Days_DataFrame(database, datetime1, datetime2, date_field_name="", date_as_index=False):
    #
    datetime2_mid_night = MidNight(datetime2)
    df = database.GetDataFrame("financial_data", "index_dailybar", [("symbol", "000001.SH")], ["date"], datetime1, datetime2_mid_night)
    # print(df)
    #
    if date_field_name != "":
        df.rename(columns={"date": date_field_name})
    else:
        date_field_name = "date"
    #
    if date_as_index:
        df.set_index(date_field_name, inplace=True)
    #
    # print(df)
    return df


def GetTradingDays(database, datetime1, datetiem2, backforword1Day=False):
    #
    filter = {"Symbol": "000001.SH"}
    filter["Date"] = {">=": datetime1, "<=": datetiem2}
    bmDataSeries = database.Find("DailyBar", "Index", filter)
    #
    if not backforword1Day:
        return bmDataSeries

    #
    found = False
    datetime0 = None
    for i in range(10):
        datetime0 = datetime1 + timedelta(days=-1 * (i+1))
        filter["Date"][">="] = datetime0
        bmDataSeries2 = database.Find("DailyBar", "Index", filter)
        # bmDataSeries2 = database.Find("Factor", "DailyReturn", filter)
        if len(bmDataSeries2) > len(bmDataSeries):
            datetime0 = bmDataSeries2[0]["date"]
            found = True
            break
    #
    if not found:
        print("Not Find Correct DateTime1 !!!")
    #
    return bmDataSeries2


def GetTradingDays_DataFrame(database, datetime1, datetiem2, backforword1Day=False):
    #
    documents = GetTradingDays(database, datetime1, datetiem2, backforword1Day=backforword1Day)
    df = DocumentsToDataFrame(documents, keep=["Date","date"], rename={"date": "Date"})
    return df



def GenerateWeekEndDays(datetime1, datetime2, weekend=6):
    df = Generate_Calender_Days_DataFrame(datetime1, datetime2)
    AddWeekColumn(df)
    # print(df)
    df["WeekDay"] = df["Date"].dt.dayofweek
    df = df[df["WeekDay"]==weekend]
    # print(df)
    return df


# 修正
def WeekNumToNumeric(year, month, weekNum):
    # 1月出现52Week
    if month == 1 and weekNum == 52:
        return (year - 1) * 100 + weekNum
    elif month == 12 and weekNum == 1:  #
        return (year + 1) * 100 + weekNum
    else:
        return year * 100 + weekNum


def AddMonthColumn(df, date_field="Date"):
    df[date_field] = pd.to_datetime(df[date_field])
    df["Year"] = df[date_field].dt.year
    df["Month"] = df[date_field].dt.month
    df["Month"] = df["Year"] * 100 + df["Month"]
    # df["Month"] = df["Year"].astype(str).str.cat(df["Month"].astype(str),sep="-")


def AddWeekColumn(df, date_field="Date"):
    def Func(x):
        year = x["Year"]
        month = x["Month"]
        weekNum = x["WeekNum"]

        # 1月出现52Week
        if month == 1 and weekNum == 52:
            return (year - 1) * 100 + weekNum
        elif month == 12 and weekNum == 1: #
            return (year + 1) * 100 + weekNum
        else:
            return year * 100 + weekNum

    #
    df[date_field] = pd.to_datetime(df[date_field])
    df["WeekNum"] = df[date_field].dt.weekofyear
    df["Year"] = df[date_field].dt.year
    df["Month"] = df[date_field].dt.month
    df["WeekDay"] = df[date_field].dt.dayofweek
    # df["Week"] = df["Year"].astype(str).str.cat(df["WeekNum"].astype(str))
    # print(df)

    # df["Week"] = df.apply(Func, axis=1)
    # df["Week"] = df.apply(lambda x: Func(x), axis=1)
    df["Week"] = df.apply(lambda x: WeekNumToNumeric(x["Year"], x["Month"], x["WeekNum"]), axis=1)

    #
    df.drop(columns=["WeekNum", "Year", "Month"], inplace=True)


def DateToWeekNum(dt):
    df = pd.DataFrame(data=[[dt]], columns=["Date"])
    df["WeekNum"] = df["Date"].dt.weekofyear
    year = df.iloc[0]["Date"].year
    month = df.iloc[0]["Date"].month
    weekNum = df.iloc[0]["WeekNum"]
    week = WeekNumToNumeric(year, month, weekNum)
    #
    return week


def ParseExcelDatetime(input, default=datetime(1900, 1, 1)):
    if input == "":
        return default
    return datetime(*xldate_as_tuple(input,0))


# 随机抽取Instrument 测试用
def RandomInstruments(database, datetime1, num=100, seed=0):
    #
    totalInstruments = FindListedInstrument(database, datetime1)
    count = len(totalInstruments)

    #
    random.seed(seed)
    l = list(range(0, count))
    r = random.sample(l, num)

    #
    instruments = []
    for i in r:
        instruments.append(totalInstruments[i])
    #
    return instruments


# Symbols 转换为 Instruments
def SymbolsToInstruments(database, symbols):

    filter = {}
    filter["$or"] = []
    for symbol in symbols:
        filter["$or"].append({"Symbol": symbol})
    #
    instruments = database.Find("Instruments", "Stock", filter)
    return instruments

# 找到最近的星期X
# x : 星期x
def Find_Recent_Weekday(datetime2, x, previous=True):
    #
    target_x = x-1
    datetime2_new = datetime2

    # weekday 0-6，星期一到星期日
    weekday = datetime2.weekday()
    while weekday != target_x:
        if previous:
            datetime2_new = datetime2_new + timedelta(days=-1)  # 往前推
        else:
            datetime2_new = datetime2_new + timedelta(days=1)  # 往后推
        #
        weekday = datetime2_new.weekday()
    #
    return datetime2_new


# prev 目标是5号，没有5号找4号
# next 目标是5号，没有5号找6号
def find_recent_trading_day(database, update_datetime, prev_next="prev", offset=0):
    #
    df_bm_bars = database.GetDataFrame("financial_data", "index_daily_bar", filter=[("symbol", "000001.SH")], sort=[("date", 1)])
    #
    if prev_next == "next":
        df_bm_bars_tmp = df_bm_bars[df_bm_bars["date"] >= update_datetime]
        trading_date = df_bm_bars_tmp.iloc[0 + offset]["date"]
    elif prev_next == "prev":
        df_bm_bars_tmp = df_bm_bars[df_bm_bars["date"] <= update_datetime]
        trading_date = df_bm_bars_tmp.iloc[-1 - offset]["date"]
    else:
        return None

    return trading_date


# 确定日期
def find_recent_date_in_table(database, database_name, table_name, update_datetime, date_field=None):
    #
    if not date_field:
        date_field = "date"
    #
    s_update_datetime = update_datetime.strftime('%Y-%m-%d')
    sql_str = "select max(" + date_field + ") from " + database_name + "." + table_name + " where " + date_field + " <=" + "'" + s_update_datetime + "'"
    # print(sql_str)
    max_date = database.ExecuteSQL(database_name, sql_str)
    recent_date = max_date[0][0]
    return recent_date


def find_minimum_date_in_table(database, database_name, table_name, date_field=None):
    #
    if not date_field:
        date_field = "date"
    #
    # s_update_datetime = update_datetime.strftime('%Y-%m-%d')
    sql_str = "select min(" + date_field + ") from " + database_name + "." + table_name
    # print(sql_str)
    max_date = database.ExecuteSQL(database_name, sql_str)
    recent_date = max_date[0][0]
    return recent_date


def find_maximum_date_in_table(database, database_name, table_name, date_field=None):
    #
    if not date_field:
        date_field = "date"
    #
    # s_update_datetime = update_datetime.strftime('%Y-%m-%d')
    sql_str = "select max(" + date_field + ") from " + database_name + "." + table_name
    # print(sql_str)
    max_date = database.ExecuteSQL(database_name, sql_str)
    recent_date = max_date[0][0]
    return recent_date


def find_recent_report_date_in_table(database, database_name, table_name, update_datetime):
    #
    s_update_datetime = update_datetime.strftime('%Y-%m-%d')
    sql_str = "select max(report_date) from " + database_name + "." + table_name + " where report_date <=" + "'" + s_update_datetime + "'"
    # print(sql_str)
    max_date = database.ExecuteSQL(database_name, sql_str)
    recent_date = max_date[0][0]
    return recent_date


def load_stock_price_2(database, profile_datetime):
    # load price
    for i in range(10):
        try_date = profile_datetime - timedelta(days=i)
        df_price = database.GetDataFrame("financial_data", "stock_daily_bar",
                                         filter=[("date", try_date)],
                                         projection=["symbol", "close", "bclose", "total_shares", "adjfactor"])  # "total_shares", "free_float_shares", "adjfactor"
        if not df_price.empty:
            break

    return df_price, try_date


# 股票价格剖面信息
def load_stock_price(database, profile_datetime):
    #
    s_update_datetime = profile_datetime.strftime('%Y-%m-%d')
    sql_str = "select max(date) from financial_data.stock_dailybar where date <=" + "'" + s_update_datetime + "'"
    max_date = database.ExecuteSQL("financial_data", sql_str)
    max_date = max_date[0][0]
    #
    df_price = database.GetDataFrame("financial_data", "stock_dailybar", filter=[("date", max_date)], projection=["symbol", "close", "bclose", "total_shares"])
    return df_price


def field_rename(df, fields, num):
    for field in fields:
        df.rename(columns={field: field + "_" + str(num)}, inplace=True)


# def field_rename_2(df, fields, pre="", app=""):
#     for field in fields:
#         df.rename(columns={field: pre + field + app}, inplace=True)


def rename_field(df, fields, suffix, inplace=False):
    rename_field = {}
    for field in fields:
        rename_field[field] = field + "_" + suffix
    df_rename = df.rename(columns=rename_field, inplace=inplace)
    return df_rename


def cold_start_calc_factor_monthly(database, datetime1, datetime2, callback):
    date_list = generate_end_date_of_month_list(datetime1, datetime2)
    #
    for update_datetime in date_list:
        print(update_datetime, "cold_start_calc_factor_monthly")
        callback(database, update_datetime)


def cold_start_calc_factor_quarterly(database, datetime1, datetime2, callback):
    date_list = generate_report_date_list(datetime1, datetime2)
    #
    for update_datetime in date_list:
        print(update_datetime, "cold_start_calc_factor_quarterly")
        callback(database, update_datetime)


def cold_start_calc_industry_factor_monthly(database, datetime1, datetime2, industry_type, callback):
    date_list = generate_end_date_of_month_list(datetime1, datetime2)
    #
    for update_datetime in date_list:
        print(update_datetime, "cold_start_calc_factor_monthly")
        callback(database, update_datetime, industry_type)


def cold_start_calc_industry_factor_quarterly(database, datetime1, datetime2, industry_type, callback):
    date_list = generate_report_date_list(datetime1, datetime2)
    #
    for update_datetime in date_list:
        print(update_datetime, "cold_start_calc_factor_quarterly")
        callback(database, update_datetime, industry_type)


#
def is_month_end(update_datetime):
    days = calendar.monthrange(update_datetime.year, update_datetime.month)
    if update_datetime == days[1]:
        return True
    return False


#
def none_to_max_date(input_date):
    if input_date == None:
        return datetime(2100, 1, 1)
    else:
        return input_date


def invalid_date_to_max_date(input_date):
    if input_date > datetime(1900, 1, 1):
        return input_date
    else:
        max_date_time = datetime(2100, 1, 1)
        return max_date_time


def invalid_date_to_none(input_date):
    if input_date > datetime(1900, 1, 1):
        return input_date
    else:
        return None


if __name__ == '__main__':

    # ---month date---
    any_datetime = datetime(2022,12,31)
    res_1 = find_recent_end_date_of_month(any_datetime)
    res_2 = find_last_end_date_of_month(any_datetime)
    #
    datetime1 = datetime(2021,1,1)
    datetime2 = datetime(2023,1,1)
    res_3 = generate_end_date_of_month_list(datetime1, datetime2, as_date=False)
    res_4 = generate_begin_date_of_month_list(datetime1, datetime2, as_date=False)

    datetime1 = datetime(2020, 12, 31)
    for diff in [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12]:
        t1 = find_month_date(datetime1, offset=diff, use_month_end=True)
        t2 = find_month_date(datetime1, offset=diff, use_month_end=False)
        # print(diff, t1, t2)

    # ---report date---
    res_2 = generate_report_date_list(datetime1, datetime2)
    for offset in [7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8]:
        t1 = find_report_date(datetime1, offset)
        # t2 = find_report_date(datetime1, offset)

    # ---release date---
    datetime2 = datetime(2021, 5, 1)
    #
    for offset in [7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8]: #
        release_date_ending = find_release_date(datetime2, offset, use_month_end=True)
        release_date_begin = find_release_date(datetime2, offset, use_month_end=False)
        # print(offset, release_date_begin.date(), release_date_ending.date())

    # ---fund release date---
    any_datetime = datetime(2023, 5, 1)
    # fr_1 = find_fund_release_date_date(any_datetime, offset=1, use_month_end=True)
    # fr_2 = find_fund_release_date_date(any_datetime, offset=1, use_month_end=False)
    for offset in [7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8]:
        fund_release_date_ending = find_fund_release_date(any_datetime, offset=offset, use_month_end=True)
        fund_release_date_begin  = find_fund_release_date(any_datetime, offset=offset, use_month_end=False)
        print(offset, fund_release_date_begin.date(), fund_release_date_ending.date())


    #
    m1 = get_month_diff_v2(datetime1, datetime2)


